package org.ihelpuoo.orm.parser;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.ihelpuoo.I;
import org.ihelpuoo.lang.CharUtils;
import org.ihelpuoo.lang.LoaderException;
import org.ihelpuoo.lang.ObjectUtils;
import org.ihelpuoo.lang.RenderException;
import org.ihelpuoo.lang.StringUtils;
import org.ihelpuoo.orm.III;
import org.ihelpuoo.parser.AbstractTextEngine;
import org.ihelpuoo.parser.token.CharChecker;

public class ObjectStringParser extends AbstractTextEngine
{
	private static final CharChecker		fieldSeparator	= new FieldSeparatorChecker();
	private static final CharChecker		fieldEndl		= new FieldEndlChecker();
	private static final Comparator<String>	sorter			= new ObjectFieldNameComparator();

	private static Map<String, Object> buildBeanDataMap(Reader reader) throws IOException
	{
		Map<String, Object> map = new HashMap<String, Object>();
		int c = reader.read();
		List<Map<String, Object>> list = new LinkedList<Map<String, Object>>();
		String name = null;
		StringBuffer nameBuffer = new StringBuffer();
		while ((int)'}' != c && -1 != c)
		{
			if (CharUtils.isWhiteSpace(c)
					|| ((null == name) && (CharChecker.MATCHED == fieldSeparator.doCheck(c) || CharChecker.MATCHED == fieldEndl
							.doCheck(c))))
			{
				c = reader.read();
				continue;
			}
			if (null == name)
			{
				while (c != -1 && CharChecker.MATCHED != fieldSeparator.doCheck(c))
				{
					nameBuffer.append((char)c);
					c = reader.read();
				}
				if (nameBuffer.length() > 0)
				{
					name = StringUtils.trim(nameBuffer.toString());
					if (name.length() > 0)
					{
						nameBuffer = new StringBuffer();
					}
					else
					{
						name = null;
					}
				}
			}
			else
			{
				if (c == (int)'{')
				{
					list.add(buildBeanDataMap(reader));
				}
				else
				{
					if (list.size() > 0)
					{
						if (list.size() == 1)
						{
							map.put(name,list.get(0));
						}
						else
						{
							map.put(name,list);
						}
						list = new LinkedList<Map<String, Object>>();
						name = null;
						nameBuffer.append((char)c);
					}
					else
					{
						StringBuffer sb = new StringBuffer();
						int re = fieldEndl.doCheck(c);
						while (c != -1)
						{
							if (CharChecker.IGNORE_IT == re)
							{
								c = reader.read();
								re = fieldEndl.doCheck(c);
								continue;
							}
							else if (CharChecker.MATCHED != re)
							{
								sb.append((char)c);
								c = reader.read();
								re = fieldEndl.doCheck(c);
								continue;
							}
							else
							{
								break;
							}
						}
						String v = StringUtils.trim(sb.toString());
						map.put(name,v);
						name = null;
					}
				}
			}
			c = reader.read();
		}
		if (list.size() > 0)
		{
			if (list.size() == 1)
			{
				map.put(name,list.get(0));
			}
			else
			{
				map.put(name,list);
			}
			name = null;
			nameBuffer.append(c);
		}
		return map;
	}

	@SuppressWarnings("unchecked")
	private static String formattedDumpMap(Map<String, Object> map, boolean escaped, int indent)
	{
		StringBuffer sb = new StringBuffer();
		CharSequence prefix = StringUtils.dup("    ",indent);
		if (null != map)
		{
			List<String> list = new ArrayList<String>(map.size());
			list.addAll(map.keySet());
			Collections.sort(list,sorter);
			for (int i = 0; i < list.size(); i++)
			{
				String name = (String)list.get(i);
				sb.append(prefix);
				sb.append(name);
				sb.append("=");
				Object v = map.get(name);
				if (v instanceof Map)
				{
					sb.append('{');
					sb.append(I.NEW_LINE);
					sb.append(formattedDumpMap((Map)v,escaped,indent + 1));
					sb.append(prefix).append('}');
				}
				else if (v instanceof List)
				{
					List vl = (List)v;
					for (int j = 0; j < vl.size(); j++)
					{
						sb.append('{');
						sb.append(I.NEW_LINE);
						sb.append(formattedDumpMap((Map)vl.get(j),escaped,indent + 1));
						sb.append(prefix).append('}');
					}
				}
				else
				{
					CharSequence cs = ObjectUtils.castObjectToCharSequence(v);
					evalCharSequence(cs,sb,escaped);
					sb.append(";");
				}
				sb.append(I.NEW_LINE);
			}
		}
		return sb.toString();
	}

	private static void evalCharSequence(CharSequence cs, StringBuffer sb, boolean escaped)
	{
		if (escaped)
		{
			for (int x = 0; x < cs.length(); x++)
			{
				char c = cs.charAt(x);
				if (c == '\\')
				{
					sb.append(c);
				}
				else if (c == ';')
				{
					sb.append('\\');
				}
				sb.append(c);
			}
		}
		else
		{
			sb.append(cs.toString());
		}
	}

	@SuppressWarnings("unchecked")
	private static String compressDumpMap(Map<String, Object> map, boolean escaped)
	{
		StringBuffer sb = new StringBuffer();
		if (null != map)
		{
			List<String> list = new ArrayList<String>(map.size());
			list.addAll(map.keySet());
			Collections.sort(list,sorter);
			for (int i = 0; i < list.size(); i++)
			{
				String name = (String)list.get(i);
				sb.append(name);
				sb.append("=");
				Object v = map.get(name);
				if (v instanceof Map)
				{
					sb.append('{');
					sb.append(compressDumpMap((Map)v,escaped));
					sb.append('}');
				}
				else if (v instanceof List)
				{
					List vl = (List)v;
					for (int j = 0; j < vl.size(); j++)
					{
						sb.append('{');
						sb.append(compressDumpMap((Map)vl.get(j),escaped));
						sb.append('}');
					}
				}
				else
				{
					CharSequence cs = ObjectUtils.castObjectToCharSequence(v);
					evalCharSequence(cs,sb,escaped);
					sb.append(";");
				}
			}
		}
		return sb.toString();
	}

	private Map<String, Object>	map;
	private byte				dumpMode;
	private boolean				isEscape;

	public ObjectStringParser()
	{
		dumpMode = III.DUMP_MODE_COMPRESS;
		isEscape = true;
	}

	public Map<String, Object> getMap()
	{
		return map;
	}

	protected void setMap(Map<String, Object> map)
	{
		this.map = map;
	}

	public int size()
	{
		return map.size();
	}

	public Object get(String key)
	{
		return map.get(key);
	}

	public void setDumpMode(byte dumpMode)
	{
		this.dumpMode = dumpMode;
	}

	public void setEscape(boolean isEscape)
	{
		this.isEscape = isEscape;
	}

	public void parse(Reader r) throws LoaderException
	{
		try
		{
			map = buildBeanDataMap(r);
		}
		catch (Exception e)
		{
			throw new LoaderException(e);
		}
	}

	public String getString() throws RenderException
	{
		if (III.DUMP_MODE_FORMATTED == dumpMode)
			return formattedDumpMap(map,isEscape,0);
		else if (III.DUMP_MODE_COMPRESS == dumpMode)
			return compressDumpMap(map,isEscape);

		throw new RenderException("Error dumpMode: " + this.dumpMode);
	}

	public void render2(Writer w) throws RenderException
	{
		try
		{
			w.append(getString());
		}
		catch (IOException e)
		{
			throw new RenderException(e);
		}
	}

	public String asString()
	{
		return this.toString();
	}

}
